﻿#include "pch.h"
#include "sbcs.h"

#define KONCE_OK						0
#define KONCE_RUN						1
#define KONCE_CANCEL					-1


//////////////////////////////////////////////////////////////////////////
// once
typedef struct kOnceList
{
	struct kOnceList*	next;
	kpointer			data;
} kOnceList;

static struct kImplOnce
{
	kOnceList*			list;
	kMutex*				lock;
	kCond*				cond;
} k_impl_once =
{
	NULL,
	NULL,
	NULL
};

// 초기화
void _k_once_init(void)
{
	k_impl_once.lock = k_mutex_new();
	k_impl_once.cond = k_cond_new();
}

// 제거
void _k_once_disp(void)
{
	k_cond_delete(k_impl_once.cond);
	k_mutex_delete(k_impl_once.lock);
}

// 리스트 있나
static kOnceList* _k_oncelist_contains(kpointer data)
{
	kOnceList* node;
	node = k_impl_once.list;

	while (node)
	{
		if (node->data == data)
			return node;

		node = node->next;
	}

	return NULL;
}

// 앞에 추가
static void _k_oncelist_prepend(kpointer data)
{
	kOnceList* node = k_new_1(kOnceList);
	node->next = k_impl_once.list;
	node->data = data;
	k_impl_once.list = node;
}

// 제거
static void _k_oncelist_remove(kpointer item)
{
	kOnceList* r = k_impl_once.list;
	kOnceList* t = k_impl_once.list;
	kOnceList* v = NULL;

	while (t)
	{
		if (t->data == item)
		{
			if (v)
				v->next = t->next;
			else
				r = t->next;

			k_delete(t);
			break;
		}

		v = t;
		t = t->next;
	}

	k_impl_once.list = r;
}

/**
 * 한번만 하기 기능.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	func		콜백 함수.
 * @param	data		콜백 데이터.
 * @return	콜백 함수의 반환 값.
 */
kpointer k_once_run(kOnce* self, kThreadFunc func, kpointer data)
{
	k_mutex_enter(k_impl_once.lock);

	while (self->status == KONCE_RUN)
		k_cond_wait(k_impl_once.cond, k_impl_once.lock);

	if (self->status != KONCE_OK)
	{
		self->status = KONCE_RUN;
		k_mutex_leave(k_impl_once.lock);

		self->value = func(data);

		k_mutex_enter(k_impl_once.lock);
		self->status = KONCE_OK;

		k_cond_broadcast(k_impl_once.cond);
	}

	k_mutex_leave(k_impl_once.lock);

	return self->value;
}

/**
 * 한번만 해보기 기능 들어가기.
 * @param [입력,반환]	value	한번만 해보기 지시자.
 * @return	성공하면 참을, 실패하면 거짓을 반환한다.
 */
kcham k_once_enter(volatile kuintptr* value)
{
	kcham ret = FALSE;

	k_mutex_enter(k_impl_once.lock);

#if _SB_64_
	if (k_atomget_long((klong volatile*)value) == 0)
#else
	if (k_atomget((kint volatile*)value) == 0)
#endif
	{
		kpointer tgt = (kpointer)value;

		if (!_k_oncelist_contains(tgt))
		{
			ret = TRUE;
			_k_oncelist_prepend(tgt);
		}
		else
		{
			do k_cond_wait(k_impl_once.cond, k_impl_once.lock);

			while (_k_oncelist_contains(tgt));
		}
	}

	k_mutex_leave(k_impl_once.lock);

	return ret;
}

/**
 * 한번만 해보기 나오기.
 * @param [입력,반환]	value	한번만 해보기 지시자.
 * @param	init		 	초기값.
 */
void k_once_leave(volatile kuintptr* value, kuintptr init)
{
	k_return_if_fail(k_atomget_ptr(value) == 0);
	k_return_if_fail(init != 0);
	k_return_if_fail(k_impl_once.list != NULL);

	k_atomset_ptr(value, init);
	k_mutex_enter(k_impl_once.lock);

	_k_oncelist_remove((kpointer)(kuintptr)value);
	k_cond_broadcast(k_impl_once.cond);

	k_mutex_leave(k_impl_once.lock);
}
